﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using NUnit.Framework;
using DomainDriver.CommonUtilities.Enums;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.GenericUnitTesting.Repositories;
using AutomotivePricingModel.BodyStyles;
using AutomotivePricingModel.DriveTrainTypes;
using AutomotivePricingModel.TransmissionTypes;
using AutomotivePricingModel.Vehicles;
using AutomotivePricingModel.ValueObjects.MissingValueOptions;
using AutomotivePricingModel.ValueObjects.YearlyFinancialAssessments;

namespace AutomotivePricingModel.UnitTests.Repositories
{
    [TestFixture(new object[] { DataSourcePersistenceType.None })]
    [TestFixture(new object[] { DataSourcePersistenceType.File })]
    [TestFixture(new object[] { DataSourcePersistenceType.Database })]
    public class VehicleRepositoryTests : RepositoryTestFixture<AutomotivePricingDomainModel, Guid, Vehicle>
    {
        private int m_FirstYear = 2000;
        private double m_FirstOriginalValue = 50000;
        private double m_FirstUpdateValue = 51000;
        private int m_SecondYear = 2005;
        private double m_SecondValue = 22000;
        private int m_ThirdYear = 2010;
        private double m_ThirdValue = 15000;

        public VehicleRepositoryTests(DataSourcePersistenceType persistenceType)
            : base(persistenceType)
        {
            // do nothing
        }

        protected override AutomotivePricingDomainModel CreateDomainModel(DataSourcePersistenceType persistenceType, bool buildModelWithConstraints)
        {
            AutomotivePricingDomainModel model = new AutomotivePricingDomainModel(persistenceType, buildModelWithConstraints);
            return model;
        }

        protected override Vehicle CreateDomainObject()
        {
            Vehicle domainObject = new Vehicle();
            domainObject.ModelYear = 2000;

            domainObject.OptionsConfiguration.BodyStyle = BodyStyleEnum.SportUtility;
            domainObject.OptionsConfiguration.DriveTrainType = DriveTrainTypeEnum.FourWheelDrive;
            domainObject.OptionsConfiguration.EngineSizeInLiters = 4.2;
            domainObject.OptionsConfiguration.HasNavigationSystem = false;
            domainObject.OptionsConfiguration.NumberOfCylinders = 20;
            domainObject.OptionsConfiguration.NumberOfSpeeds = 5;
            domainObject.OptionsConfiguration.TransmissionType = TransmissionTypeEnum.Manual;

            domainObject.PriceHistory.MissingValueOption = MissingValueOptionEnum.Null;
            domainObject.PriceHistory.SetValue(m_FirstYear, m_FirstOriginalValue);
            domainObject.PriceHistory.SetValue(m_SecondYear, m_SecondValue);

            return domainObject;
        }

        protected override void UpdateDomainObject(Vehicle domainObject, ref bool objectCanBeUpdated)
        {
            objectCanBeUpdated = true;
            domainObject.ModelYear += 10;
            domainObject.OptionsConfiguration.NumberOfCylinders = 6;

            domainObject.PriceHistory.MissingValueOption = MissingValueOptionEnum.Zero;
            domainObject.PriceHistory.SetValue(m_FirstYear, m_FirstUpdateValue);
            domainObject.PriceHistory.RemoveValue(m_SecondYear);
            domainObject.PriceHistory.SetValue(m_ThirdYear, m_ThirdValue);
        }

        private Dictionary<Guid, Vehicle> GetVehiclesInDatabase()
        {
            DbContext dbContext = DomainModel.DataProvider.DataSource as DbContext;

            IQueryable<Vehicle> querable = null;
            Dictionary<Guid, Vehicle> results = new Dictionary<Guid, Vehicle>();
            if (dbContext != null)
            {
                querable = dbContext.Set<Vehicle>().Where<Vehicle>((Vehicle vehicle) => true);
                foreach (Vehicle vehicle in querable)
                {
                    results.Add(vehicle.Key, vehicle);
                }
            }
            return results;
        }

        private Dictionary<Guid, YearlyFinancialAssessment> GetYearlyFinancialAssessmentsInDatabase()
        {
            DbContext dbContext = DomainModel.DataProvider.DataSource as DbContext;

            IQueryable<YearlyFinancialAssessment> querable = null;
            Dictionary<Guid, YearlyFinancialAssessment> results = new Dictionary<Guid, YearlyFinancialAssessment>();
            if (dbContext != null)
            {
                querable = dbContext.Set<YearlyFinancialAssessment>().Where<YearlyFinancialAssessment>((YearlyFinancialAssessment finAss) => true);
                foreach (YearlyFinancialAssessment finAss in querable)
                {
                    results.Add(finAss.Key, finAss);
                }
            }
            return results;
        }

        private Dictionary<Guid, Dictionary<int, YearValueObservation>> GetYearValueObservationsInDatabase()
        {
            DbContext dbContext = DomainModel.DataProvider.DataSource as DbContext;

            IQueryable<YearValueObservation> querable = null;
            Dictionary<Guid, Dictionary<int, YearValueObservation>> results = new Dictionary<Guid, Dictionary<int, YearValueObservation>>();
            if (dbContext != null)
            {
                querable = dbContext.Set<YearValueObservation>().Where<YearValueObservation>((YearValueObservation finObs) => true);
                foreach (YearValueObservation finObs in querable)
                {
                    if (!results.ContainsKey(finObs.ParentYearlyFinancialAssessmentId))
                    { results.Add(finObs.ParentYearlyFinancialAssessmentId, new Dictionary<int, YearValueObservation>()); }

                    results[finObs.ParentYearlyFinancialAssessmentId].Add(finObs.Year, finObs);
                }
            }
            return results;
        }

        [Test]
        public void VerifyRepositoryAfterSingleAddWithAggregateInspection()
        {
            if (DomainModel.PersistenceType != DataSourcePersistenceType.Database)
            { Assert.Pass("This test can only be run under the database persistence strategy."); }

            AutomotivePricingDatabaseContext dbContext = DomainModel.DataProvider.DataSource as AutomotivePricingDatabaseContext;
            int expectedCount = InitialRepositoryCount;

            Dictionary<Guid, Vehicle> originalVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> originalFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> originalFinObses = GetYearValueObservationsInDatabase();

            Vehicle originalObject = CreateDomainObject();
            Vehicle reloadedObject = null;

            RepositoryToTest.Add(originalObject);
            expectedCount++;

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            Dictionary<Guid, Vehicle> postAddVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> postAddFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> postAddFinObses = GetYearValueObservationsInDatabase();

            Assert.AreEqual(originalVehicles.Count + 1, postAddVehicles.Count);
            Assert.AreEqual(originalFinAsses.Count + 1, postAddFinAsses.Count);
            Assert.AreEqual(originalFinObses.Count + 1, postAddFinObses.Count);
            Assert.AreEqual(2, postAddFinObses[originalObject.PriceHistory.Key].Count);
            bool firstYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_FirstYear);
            bool secondYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_SecondYear);
            bool thirdYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_ThirdYear);
            Assert.IsTrue(firstYearFound);
            Assert.IsTrue(secondYearFound);
            Assert.IsFalse(thirdYearFound);
            Assert.AreEqual(m_FirstOriginalValue, postAddFinObses[originalObject.PriceHistory.Key][m_FirstYear].Value);
            Assert.AreEqual(m_SecondValue, postAddFinObses[originalObject.PriceHistory.Key][m_SecondYear].Value);

            SaveAndReloadDomainModel();

            reloadedObject = RepositoryToTest.Read(originalObject.Key);

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            Assert.AreNotSame(originalObject, reloadedObject);
            Assert.AreEqual(originalObject, reloadedObject);

            Dictionary<Guid, Vehicle> postReloadVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> postReloadFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> postReloadFinObses = GetYearValueObservationsInDatabase();

            Assert.AreEqual(postAddVehicles.Count, postReloadVehicles.Count);
            Assert.AreEqual(postAddFinAsses.Count, postReloadFinAsses.Count);
            Assert.AreEqual(postAddFinObses.Count, postReloadFinObses.Count);
            Assert.AreEqual(2, postReloadFinObses[originalObject.PriceHistory.Key].Count);
            firstYearFound = postReloadFinObses[originalObject.PriceHistory.Key].ContainsKey(m_FirstYear);
            secondYearFound = postReloadFinObses[originalObject.PriceHistory.Key].ContainsKey(m_SecondYear);
            thirdYearFound = postReloadFinObses[originalObject.PriceHistory.Key].ContainsKey(m_ThirdYear);
            Assert.IsTrue(firstYearFound);
            Assert.IsTrue(secondYearFound);
            Assert.IsFalse(thirdYearFound);
            Assert.AreEqual(m_FirstOriginalValue, postReloadFinObses[originalObject.PriceHistory.Key][m_FirstYear].Value);
            Assert.AreEqual(m_SecondValue, postReloadFinObses[originalObject.PriceHistory.Key][m_SecondYear].Value);
        }

        [Test]
        public virtual void VerifyRepositoryAfterSingleUpdateAndRemoveWithAggregateInspection()
        {
            if (DomainModel.PersistenceType != DataSourcePersistenceType.Database)
            { Assert.Pass("This test can only be run under the database persistence strategy."); }

            AutomotivePricingDatabaseContext dbContext = DomainModel.DataProvider.DataSource as AutomotivePricingDatabaseContext;
            int expectedCount = InitialRepositoryCount;

            Dictionary<Guid, Vehicle> originalVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> originalFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> originalFinObses = GetYearValueObservationsInDatabase();

            bool objectCanBeUpdated = true;
            Vehicle originalObject = CreateDomainObject();
            Vehicle copiedObject = default(Vehicle);
            Vehicle reloadedObject = default(Vehicle);

            RepositoryToTest.Add(originalObject);
            expectedCount++;

            SaveAndReloadDomainModel();
            reloadedObject = RepositoryToTest.Read(originalObject.Key);

            Dictionary<Guid, Vehicle> postAddVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> postAddFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> postAddFinObses = GetYearValueObservationsInDatabase();

            Assert.AreEqual(originalVehicles.Count + 1, postAddVehicles.Count);
            Assert.AreEqual(originalFinAsses.Count + 1, postAddFinAsses.Count);
            Assert.AreEqual(originalFinObses.Count + 1, postAddFinObses.Count);
            Assert.AreEqual(2, postAddFinObses[originalObject.PriceHistory.Key].Count);
            bool firstYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_FirstYear);
            bool secondYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_SecondYear);
            bool thirdYearFound = postAddFinObses[originalObject.PriceHistory.Key].ContainsKey(m_ThirdYear);
            Assert.IsTrue(firstYearFound);
            Assert.IsTrue(secondYearFound);
            Assert.IsFalse(thirdYearFound);
            Assert.AreEqual(m_FirstOriginalValue, postAddFinObses[originalObject.PriceHistory.Key][m_FirstYear].Value);
            Assert.AreEqual(m_SecondValue, postAddFinObses[originalObject.PriceHistory.Key][m_SecondYear].Value);

            originalObject = RepositoryToTest.Read(originalObject.Key);
            copiedObject = originalObject.Copy();
            UpdateDomainObject(originalObject, ref objectCanBeUpdated);
            Assert.AreNotSame(originalObject, copiedObject);
            Assert.AreNotEqual(originalObject, copiedObject);

            copiedObject = originalObject.Copy();
            RepositoryToTest.Update(originalObject);
            originalObject = RepositoryToTest.Read(originalObject.Key);
            Assert.AreNotSame(originalObject, copiedObject);
            Assert.AreEqual(originalObject, copiedObject);

            SaveAndReloadDomainModel();
            reloadedObject = RepositoryToTest.Read(originalObject.Key);

            Dictionary<Guid, Vehicle> postUpdateVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> postUpdateFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> postUpdateFinObses = GetYearValueObservationsInDatabase();

            Assert.AreEqual(originalVehicles.Count + 1, postUpdateVehicles.Count);
            Assert.AreEqual(originalFinAsses.Count + 1, postUpdateFinAsses.Count);
            Assert.AreEqual(originalFinObses.Count + 1, postUpdateFinObses.Count);
            Assert.AreEqual(2, postUpdateFinObses[originalObject.PriceHistory.Key].Count);
            firstYearFound = postUpdateFinObses[originalObject.PriceHistory.Key].ContainsKey(m_FirstYear);
            secondYearFound = postUpdateFinObses[originalObject.PriceHistory.Key].ContainsKey(m_SecondYear);
            thirdYearFound = postUpdateFinObses[originalObject.PriceHistory.Key].ContainsKey(m_ThirdYear);
            Assert.IsTrue(firstYearFound);
            Assert.IsFalse(secondYearFound);
            Assert.IsTrue(thirdYearFound);
            Assert.AreEqual(m_FirstUpdateValue, postUpdateFinObses[originalObject.PriceHistory.Key][m_FirstYear].Value);
            Assert.AreEqual(m_ThirdValue, postUpdateFinObses[originalObject.PriceHistory.Key][m_ThirdYear].Value);

            RepositoryToTest.Remove(originalObject.Key);
            expectedCount--;

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsFalse(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsFalse(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            SaveAndReloadDomainModel();

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsFalse(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsFalse(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            Dictionary<Guid, Vehicle> postRemoveVehicles = GetVehiclesInDatabase();
            Dictionary<Guid, YearlyFinancialAssessment> postRemoveFinAsses = GetYearlyFinancialAssessmentsInDatabase();
            Dictionary<Guid, Dictionary<int, YearValueObservation>> postRemoveFinObses = GetYearValueObservationsInDatabase();

            Assert.AreEqual(originalVehicles.Count, postRemoveVehicles.Count);
            Assert.AreEqual(originalFinAsses.Count, postRemoveFinAsses.Count);
            Assert.AreEqual(originalFinObses.Count, postRemoveFinObses.Count);
            Assert.IsFalse(postRemoveFinObses.ContainsKey(originalObject.PriceHistory.Key));
        }
    }
}